2024_moulin_mes_chers_compatriotes.py

#

SPDX-FileCopyrightText: 2025 Alyssar Dunia & Loïs Anseel SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

import bpy
import math
import bmesh
import mathutils
import random
import pprint
#

CLEAN UP SCENE

bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.ops.outliner.orphans_purge()
#

OPTIMIZED GEOMETRY CREATION (one mesh subdivided rather than accumulation of individual elements)

def generate_object_from_grid(grid_pattern, layers, obj_name, position=(0, 0, 0)):
#

Dimensions of each cube

    cube_size = 0.5
#

Create a new mesh and object

    mesh = bpy.data.meshes.new(obj_name)
    obj = bpy.data.objects.new(obj_name, mesh)
    bpy.context.collection.objects.link(obj)
#

Generate vertices and faces

    vertices = []
    faces = []
    vertex_index = 0

    for z in range(layers):
        for y, row in enumerate(grid_pattern):
            for x, cell in enumerate(row):
                if cell == 1:  # If the cell is 1, add a cube
#

Define 8 vertices of the cube

                    cube_vertices = [
                        (x * cube_size, y * cube_size, z * cube_size),
                        (x * cube_size + cube_size, y * cube_size, z * cube_size),
                        (
                            x * cube_size + cube_size,
                            y * cube_size + cube_size,
                            z * cube_size,
                        ),
                        (x * cube_size, y * cube_size + cube_size, z * cube_size),
                        (x * cube_size, y * cube_size, z * cube_size + cube_size),
                        (
                            x * cube_size + cube_size,
                            y * cube_size,
                            z * cube_size + cube_size,
                        ),
                        (
                            x * cube_size + cube_size,
                            y * cube_size + cube_size,
                            z * cube_size + cube_size,
                        ),
                        (
                            x * cube_size,
                            y * cube_size + cube_size,
                            z * cube_size + cube_size,
                        ),
                    ]
#

Define 6 faces of the cube

                    cube_faces = [
                        (
                            vertex_index,
                            vertex_index + 1,
                            vertex_index + 2,
                            vertex_index + 3,
                        ),
                        (
                            vertex_index + 4,
                            vertex_index + 5,
                            vertex_index + 6,
                            vertex_index + 7,
                        ),
                        (
                            vertex_index,
                            vertex_index + 1,
                            vertex_index + 5,
                            vertex_index + 4,
                        ),
                        (
                            vertex_index + 1,
                            vertex_index + 2,
                            vertex_index + 6,
                            vertex_index + 5,
                        ),
                        (
                            vertex_index + 2,
                            vertex_index + 3,
                            vertex_index + 7,
                            vertex_index + 6,
                        ),
                        (
                            vertex_index + 3,
                            vertex_index + 0,
                            vertex_index + 4,
                            vertex_index + 7,
                        ),
                    ]
#

Add vertices and faces to the lists

                    vertices.extend(cube_vertices)
                    faces.extend(cube_faces)
#

Update vertex index for the next cube

                    vertex_index += 8
#

Create the mesh from vertices and faces

    mesh.from_pydata(vertices, [], faces)
    mesh.update()
#

Set object position

    obj.location = position
#
def create_rotated_structure(
    grid_pattern,
    num_stacks,
    obj_name,
    position=(0, 0, 0),
    initial_rotation=2,
    rotation_increment=2,
    increment_factor=0.5,
    scale_ones=1.0,
):
#

Grid and object parameters

    grid_size = len(grid_pattern[0])
    cell_length = 1
    cell_height = 0.75
    cell_depth = 1
#

Create a new mesh and object

    mesh = bpy.data.meshes.new(obj_name)
    obj = bpy.data.objects.new(obj_name, mesh)
    bpy.context.collection.objects.link(obj)
#

Initialize bmesh for efficient geometry creation

    bm = bmesh.new()
#
    def create_layer(base_z, rotation_angle):
        cos_theta = math.cos(rotation_angle)
        sin_theta = math.sin(rotation_angle)

        for row, pattern_row in enumerate(grid_pattern):
            for col, cell in enumerate(pattern_row):
                if cell == 1:
                    x = (
                        col * cell_length
                        - (grid_size * cell_length / 2)
                        + (cell_length / 2)
                    )
                    y = (
                        row * cell_depth
                        - (grid_size * cell_depth / 2)
                        + (cell_depth / 2)
                    )

                    rotated_x = cos_theta * x - sin_theta * y
                    rotated_y = sin_theta * x + cos_theta * y
#

Create a cube and apply scaling for “1”

                    bmesh.ops.create_cube(
                        bm,
                        size=1.0,
                        matrix=mathutils.Matrix.Translation(
                            (rotated_x, rotated_y, base_z)
                        ),
                    )
                    bmesh.ops.scale(
                        bm,
                        verts=bm.verts[-8:],
                        vec=(
                            cell_length / 2 * scale_ones,
                            cell_depth / 2 * scale_ones,
                            cell_height / 2 * scale_ones,
                        ),
                    )
#

Create layers

    current_increment = rotation_increment
    for stack in range(num_stacks):
        layer_z = stack * cell_height
        angle = math.radians(initial_rotation + current_increment * stack)
        create_layer(layer_z, angle)
        current_increment += increment_factor
#

Finalize mesh

    bm.to_mesh(mesh)
    bm.free()
#

Set object position

    obj.location = position
#

DEFINE BINARY PATTERN

pattern = [
    random.choice([1, 0]) for _ in range(18)
]  # [1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1]
grid_pattern = []
pattern.reverse()
grid_pattern.append(pattern.copy())
pattern.reverse()
for i in range(len(pattern) - 2):
    line = []
    line.append(pattern[i + 1])
    line.extend([0 for _ in range(len(pattern) - 2)])
    line.append(pattern[-i - 1])
    grid_pattern.append(line.copy())
grid_pattern.append(pattern.copy())
print("Pattern:")
pprint.pprint(grid_pattern)
#

PARAMETER AND EXECUTE OBJECT CREATION Number of layers to stack on Z-axis for the grid object

grid_layers = 100
#

Create grid object

generate_object_from_grid(
    grid_pattern, grid_layers, "GridObject", position=(-4.25, -4.25, -0.5)
)
#

Parameters for rotated structure

num_stacks = 180
initial_rotation = 2
rotation_increment = 2
increment_factor = 1
scale_ones = 1
#

Create rotated structure

create_rotated_structure(
    grid_pattern,
    num_stacks,
    "RotatedStructure",
    position=(0, 0, 0),
    initial_rotation=initial_rotation,
    rotation_increment=rotation_increment,
    increment_factor=increment_factor,
    scale_ones=scale_ones,
)
#

ISOLATE RANDOM PORTION OF TOWER Create a cube for intersection

bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), scale=(7, 7, 5))
#

Translate the cube randomly along the Z-axis

bpy.ops.transform.translate(value=(0, 0, random.uniform(0, 50)))
#

Select both objects and the cube

grid_object = bpy.data.objects["GridObject"]
rotated_structure = bpy.data.objects["RotatedStructure"]
intersect_cube = bpy.data.objects["Cube"]
#

Ensure both objects are selected

grid_object.select_set(True)
rotated_structure.select_set(True)
#

Add a boolean modifier to both objects

bpy.context.view_layer.objects.active = (
    grid_object  # Set an object as active for context
)
for obj in [grid_object, rotated_structure]:
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.modifier_add(type="BOOLEAN")
    obj.modifiers["Boolean"].operation = "INTERSECT"
    obj.modifiers["Boolean"].solver = "FAST"
    obj.modifiers["Boolean"].object = intersect_cube
#

Apply modifiers to all selected objects at once

bpy.ops.object.select_all(action="DESELECT")
grid_object.select_set(True)
rotated_structure.select_set(True)
bpy.ops.object.convert(target="MESH")  # Applies all modifiers for selected objects
#

Delete the cube

bpy.ops.object.select_all(action="DESELECT")
intersect_cube.select_set(True)
bpy.ops.object.delete(use_global=False)